#include "LS3A5000.h"

LS3A5000_FLASH_MEMMAP Lsflash = {0};
STATIC UINT8 ValueRegSpcr  = 0xFF;
STATIC UINT8 ValueRegSpsr  = 0xFF;
STATIC UINT8 ValueRegSper  = 0xFF;
STATIC UINT8 ValueRegParam = 0xFF;
STATIC UINT64 SPI_REG_BASE = 0x0;
static LS3A5000_FLASH_MEMMAP* get_VirtAddr_from_context(const struct flashctx *flash)
{
	if (!flash || !flash->mst || !flash->mst->spi.data) {
		msg_perr("Unable to extract Virt Addr from flash context.\n");
		return NULL;
	}
	return flash->mst->spi.data;
}

static VOID SpiFlashSetCs (INTN Bit)
{
  UINT8          Val;

  if (Bit)
    Val = 0x11;
  else
    Val = 0x01;
  REGSET(CSCCHIPERASET,Val);
}

static UINT8 SpiFlashWriteByteCmd (UINT8 Value)
{
  UINT8 Ret;
  INT32 TimeOut = 100000;

  REGSET (REG_SPDR, Value);
  while ((REGGET(REG_SPSR) & 0x01) && TimeOut--);
  Ret = REGGET(REG_SPDR);
  if (TimeOut < 0)
    msg_perr("cmd %x send fail.\n", Value);

  return Ret;
}

static UINT8 SpiFlashReadByteCmd (VOID)
{
  return SpiFlashWriteByteCmd(0x00);
}

static int LS_spi_send_command(const struct flashctx *flash,
				unsigned int writecnt, unsigned int readcnt,
				const unsigned char *writearr,
				unsigned char *readarr)
{
    uint32_t i;


	if (writecnt > 9 || readcnt > 8 || writecnt == 0) {
		msg_perr("%s: Invalid read/write count for send command.\n",
                         __func__);
		return SPI_GENERIC_ERROR;
	}

	LS3A5000_FLASH_MEMMAP *pVirtAddr = get_VirtAddr_from_context(flash);
    if (NULL == pVirtAddr)
		return SPI_GENERIC_ERROR;

    SPI_REG_BASE = (UINT64)pVirtAddr->spi_ctrl_reg_base_virt;

    REGSET(REG_SOFTCS,0x01);
    for (i = 0; i < writecnt; i++)
    {
        SpiFlashWriteByteCmd (writearr[i]);
    }
    

    for (i = 0; i < readcnt; i++)
    {
        readarr[i] = SpiFlashReadByteCmd ();
       // msg_perr("read %x %d.\n", readarr[i], i);
    }

    REGSET(REG_SOFTCS,0x11);

	return 0;
}

static UINTN SpiFlashSpiReadBuffer (UINTN  Offset, VOID  *Buffer, UINTN  Num)
{
  UINTN   Pos = Offset;
  UINT8  *Buf = (UINT8 *) Buffer;
  UINTN   Index;

  REGSET(REG_SOFTCS,0x01);
  SpiFlashWriteByteCmd (FAST_READ);
  SpiFlashWriteByteCmd ((Pos >> 16) & 0xff);
  SpiFlashWriteByteCmd ((Pos >> 8) & 0xff);
  SpiFlashWriteByteCmd (Pos & 0xff);
  SpiFlashWriteByteCmd (0);
  for (Index = 0; Index < Num; Index++)
    Buf[Index] = SpiFlashReadByteCmd ();

  REGSET(REG_SOFTCS,0x11);

  return Index;
}

static VOID ResetSfcParamReg()
{
  REGSET(REG_PARAM,0x47);
}

static VOID SpiFlashReset (VOID)
{

  REGSET(REG_SPCR, ValueRegSpcr);
  REGSET(REG_SPSR, ValueRegSpsr);
  REGSET(REG_SPER, ValueRegSper);
  REGSET(REG_PARAM, ValueRegParam);

  ValueRegSpcr  = 0xFF;
  ValueRegSpsr  = 0xFF;
  ValueRegSper  = 0xFF;
  ValueRegParam = 0xFF;
}

static int LS_spi_read(struct flashctx *flash, uint8_t *buf,
			unsigned int start, unsigned int len)
{
   
    SpiFlashSpiReadBuffer (start, buf, len);
    return 0;
}

static UINT8 SpiFlashReadStatus(VOID)
{
  UINT8 Val;

  REGSET(REG_SOFTCS,0x01);
  SpiFlashWriteByteCmd(RDSR);
  Val = SpiFlashReadByteCmd();
  REGSET(REG_SOFTCS,0x11);

  return Val;
}

static UINTN SpiFlashWait (VOID)
{
  UINTN Ret;
  INT32 TimeOut = 100000;
  INT32 Count = 5;
  do {
    Ret = SpiFlashReadStatus();
    if (TimeOut == 0)
    {
      Count--;
      if (Count < 0 )
        msg_perr("imeOut Count is %d, Status Reg=0x%llx\n",Count, Ret);
      TimeOut = 100000;
    }
  } while ((Ret & 1) && TimeOut--);

  return Ret;
}

static VOID SpiFlashWriteByte (UINTN  Addr0, UINTN  Addr1, UINTN  Addr2, UINT8  Buf)
{
  SpiFlashSetCs (0);
  SpiFlashWriteByteCmd (WREN);
  SpiFlashSetCs (1);

  SpiFlashSetCs (0);
  SpiFlashWriteByteCmd (BYTE_WRITE);
  SpiFlashWriteByteCmd (Addr2);
  SpiFlashWriteByteCmd (Addr1);
  SpiFlashWriteByteCmd (Addr0);
  SpiFlashWriteByteCmd (Buf);
  SpiFlashSetCs (1);
  SpiFlashWait ();
}

static int LS_spi_write_256(struct flashctx *flash, const uint8_t *buf,
				unsigned int start, unsigned int len)
{
	UINTN  Pos = start;
    UINT8  *Buf = (UINT8 *) buf;
    UINTN  Index = 0;
    UINTN  Addr0;
    UINTN  Addr1;
    UINTN  Addr2;

    while (Index < len) {
        Addr0 = (Pos + Index) & 0xff;
        Addr1 = ((Pos + Index) >> 8) & 0xff;
        Addr2 = ((Pos + Index) >> 16) & 0xff;

        SpiFlashWriteByte (Addr0, Addr1, Addr2, Buf[Index]);
        Index++;
    }

	return 0;
}

static int LS_spi_write_aai(struct flashctx *flash, const uint8_t *buf,
				 unsigned int start, unsigned int len)
{
	msg_perr("%s: Not Support.\n", __func__);

	return 0;
}

static int LS_spi_shutdown(void *data)
{
    LS3A5000_FLASH_MEMMAP *pVirtAddr = data;

    SpiFlashReset();
    ResetSfcParamReg();

    if (pVirtAddr->spi_ctrl_reg_base_virt)
    {
        physunmap (pVirtAddr->spi_ctrl_reg_base_virt, SPI_REG_LENGTH);
        pVirtAddr->spi_ctrl_reg_base_virt = NULL;
    }

	return 0;
}

static const struct spi_master spi_master_LS3A5000 = {
	.max_data_read = 9,
	.max_data_write = 9,
	.command = LS_spi_send_command,
	.multicommand = default_spi_send_multicommand,
	.read = LS_spi_read,
	.write_256 = LS_spi_write_256,
	.write_aai = LS_spi_write_aai,
	.shutdown = LS_spi_shutdown,
};

static void *NullMap(const char *descr, uintptr_t phys_addr, size_t len)
{
    return Lsflash.spi_ctrl_reg_base_virt;
}

static void NullUnMap(void *virt_addr, size_t len)
{
    return;
}

static VOID SpiFlashInit (VOID)
{
  if (ValueRegSpcr == 0xFF) {
    ValueRegSpcr = REGGET(REG_SPCR);
  }
  if (ValueRegSpsr == 0xFF) {
    ValueRegSpsr = REGGET(REG_SPSR);
  }
  if (ValueRegSper == 0xFF) {
    ValueRegSper = REGGET(REG_SPER);
  }
  if (ValueRegParam == 0xFF) {
    ValueRegParam = REGGET(REG_PARAM);
  }

  //[spre:spr] [01:00] means clk_div=8
  REGSET(REG_SPCR, 0x50);//[1:0] [0:0]spr
  REGSET(REG_SPSR, 0xc0);
  REGSET(REG_SPER, 0x05);//[1:0] [0:1]spre
  REGSET(REG_PARAM, 0x40);
  REGSET(REG_TIME, 0x01);
}

static int Ls3A5000_init(void)
{
    Lsflash.spi_ctrl_reg_base_virt = physmap("SPICtrl", SPI_CTRL_REG_BASE, SPI_REG_LENGTH);
    SPI_REG_BASE = (UINT64)Lsflash.spi_ctrl_reg_base_virt;
    SpiFlashInit();
    return register_spi_master(&spi_master_LS3A5000, &Lsflash);
}

const struct programmer_entry programmer_LS3A5000 = {
	.name			= "LS3A5000",
	.type			= OTHER,
	.devs.note		= "LS3A5000.\n",
	.init			= Ls3A5000_init,
	.map_flash_region	= NullMap,
	.unmap_flash_region	= NullUnMap,
	.delay			= internal_delay,
};